package Testing;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.List;

import DbObjects.Person;
import MockDb.StaticMockDb;
import MockDb.StaticMockDbInitiailizer;
import Remote.ISimpleService;
import Remote.PersonService;

public class TestClient
{
	private static ISimpleService<Person> service = null;
	public StringBuffer testResults = new StringBuffer();

	private final static String LIGHT_LOAD_FILENAME = "LightLoad.xml";
	private final static String HEAVY_LOAD_FILENAME = "HeavyLoad.xml";
	private static TestClientConfiguration testConfig = new TestClientConfiguration();
	
	public final Runnable doReadThread;
	public final Runnable doListThread;
	
	public TestClient()
	{
		doReadThread = new Runnable()
		{
			public int optionNumber = 1;
			
			public void run()
			{
				TestClient.this.DoReadThreaded(service, optionNumber, Thread.currentThread().getId());
			}
		};
		
		doListThread = new Runnable()
		{
			public void run()
			{
				TestClient.this.DoListThreaded(service, Thread.currentThread().getId());
			}
		};
	}
	
	public static void main(String[] args) throws RemoteException
	{		
		String baseuri = "rmi://osprey.unf.edu/";
		//grab the service (should be the remote object on the server, so the
		//db instance will be created there)
		try
		{
			Registry r = LocateRegistry.getRegistry(args[0], Integer.parseInt(args[1]));
			service = (ISimpleService<Person>)r.lookup("RMIPerson");//new PersonService(new StaticMockDb(new StaticMockDbInitiailizer()));
		}
		catch(Exception e)
		{
			System.out.println("Incorrect connection information please pass the host address in the first argumnet and the port in the second argument.");
			e.printStackTrace();
		}
		
		//hold input data
		String input = null;
		
		//buffered reader
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader reader = new BufferedReader(isr);
		
		try 
		{
			//title
			System.out.println("Java RMI/CORBA Project");
			
			//dumb console app: let the user enter stuff until they're done
			do
			{
				System.out.println("Enter a command:");
				System.out.println("1 - Read a record");
				System.out.println("2 - Update a record");
				System.out.println("3 - Display a list");
				System.out.println("4 - Run record commands");
				System.out.println("5 - Run list commands");
				System.out.println("6 - Do reporting");
				System.out.println("Q - Quit");
				System.out.println("");
				System.out.print("Choice: ");
				input = reader.readLine();
				
				//validate the input
				if(ValidateInput(input, true))
				{
					//if they quit, then let them go
					if(input.contentEquals("q") || input.contentEquals("Q"))
						System.out.println("Thanks. Goodbye.");
					else //do some work
					{
						//get the integer and switch it
						int option = Integer.parseInt(input);
						
						switch(option)
						{
							case 1: //read 
								DoRead(service, reader, false);
								break;
							case 2: //update
								DoUpdate(service, reader);
								break;
							case 3: //list
								DoList(service, reader, false);
								break;
							case 4:
								DoRead(service, reader, true);
								break;
							case 5:
								DoList(service, reader, true);
								break;
							case 6:
								DoReporting(service, reader);
								break;
							default: //unknown option
								System.out.println("Invalid option. Please review the menu and try again.");
						}
					}
				}
				else //do error handling
				{
					System.out.println("Error. Please double-check your input.");
				}
			}while(!input.contentEquals("q") && !input.contentEquals("Q"));
		} 
		catch (Exception e) 
		{
			//cry a little inside due to poor exception handling...
			e.printStackTrace();
		}
	}
	
	private static void DoReporting(ISimpleService<Person> service, BufferedReader reader)
	{
		//reporting
		String option = null;
		boolean done = false;
		int selection = 0;
		
		//option do light load testing (results for 1 thru 50 clients in increments of 5)
		//option do heavy load testing (derp)
		
		try
		{
			System.out.println("Reporting options:");
			System.out.println("1 - Light Load Testing");
			System.out.println("2 - Heavy Load Testing");
			System.out.println("Q - Quit");
			System.out.println("");
			System.out.print("Choice: ");
			option = reader.readLine();
			
			do
			{
				//validate the input
				if(ValidateInput(option, true))
				{
					if(!option.contentEquals("q") && !option.contentEquals("Q"))
					{
						selection = Integer.parseInt(option);
						
						if(selection == 1)
						{
							//delete the file if it exists
							File lightLoadFile = new File(LIGHT_LOAD_FILENAME);
							
							if(lightLoadFile.exists())
								lightLoadFile.delete();
							
							int numClients = testConfig.getLightLoadClientStart();							
							
							WriteResults(LIGHT_LOAD_FILENAME, "<lightloadtest>\n");

							TestClient client = new TestClient();
							//do light load test... thread count: 5, 10, 15... up to 50
							//each thread executes 10 times
							while(numClients <= testConfig.getLightLoadClientMax())
							{
								WriteResults(LIGHT_LOAD_FILENAME, "\t<testgroup clients=\"" + numClients + "\">\n");
								
								List threads = new ArrayList<Thread>();
								
								for(int x=0; x<numClients; x++)
								{
									Thread t = new Thread(client.doReadThread);
									t.start();
									threads.add(t);
								}
								
								for(int y=0; y<threads.size(); y++)
								{
									((Thread)threads.get(y)).join();
								}
								
								WriteResults(LIGHT_LOAD_FILENAME, "\t</testgroup>\n");
								
								numClients += testConfig.getLightLoadClientIncrement();
							}
							
							WriteResults(LIGHT_LOAD_FILENAME, "</lightloadtest>\n");
							
							System.out.println("Light load test complete.");
						}
						else if(selection == 2)
						{
							//delete the file if it exists
							File heavyLoadFile = new File(HEAVY_LOAD_FILENAME);
							
							if(heavyLoadFile.exists())
								heavyLoadFile.delete();
							
							int numClients = testConfig.getHeavyLoadClientStart();							
							
							WriteResults(HEAVY_LOAD_FILENAME, "<heavyloadtest>\n");
							
							TestClient client = new TestClient();
							
							while(numClients <= testConfig.getHeavyLoadClientMax())
							{
								WriteResults(HEAVY_LOAD_FILENAME, "\t<testgroup clients=\"" + numClients + "\">\n");
								
								List threads = new ArrayList<Thread>();
								
								//do heavy load test
								for(int x=0; x<numClients; x++)
								{
									Thread t = new Thread(client.doListThread);
									t.start();
									threads.add(t);
								}
								
								for(int y=0; y<threads.size(); y++)
								{
									((Thread)threads.get(y)).join();
								}
								
								System.out.println("Client group " + numClients + " complete");
								
								WriteResults(HEAVY_LOAD_FILENAME, "\t</testgroup>\n");
								
								numClients += testConfig.getHeavyLoadClientIncrement();
							}
							
							WriteResults(HEAVY_LOAD_FILENAME, "</heavyloadtest>\n");
							
							System.out.println("Heavy load test complete");
						}
					}
					
					System.out.println("Done.");
					reader.readLine();
					
					//we're done
					done = true;
				}
				else
				{
					System.out.println("That is an invalid id. Please try again.");
				}
			}while(!done);
			
			System.out.println("\nDone reporting. Press <enter> to continue");
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private static void DoRead(ISimpleService<Person> service, BufferedReader reader, boolean automate)
	{
		String option = null;
		boolean done = false;
		
		try
		{
			do
			{
				System.out.print("Enter an id ('q' or 'Q' to quit): ");
				option = reader.readLine();
				
				//validate the input
				if(ValidateInput(option, true))
				{
					if(!option.contentEquals("q") && !option.contentEquals("Q"))
					{
						boolean firstrun = true;
						//attempt to get the record from the service
						String times = "";
						int i = 1;
						while(automate || firstrun)
						{
							if(automate)
								Thread.sleep(1000);
							long elapsed = 0;
							long ms = System.currentTimeMillis();
							Person person = service.Read(Integer.parseInt(option));
							elapsed = System.currentTimeMillis() - ms;
							
							if(person == null)
								System.out.println("Person not found.");
							else
								System.out.println("Found: " + person.toString());
							times = i + ": " + elapsed + "ms";
							i++;
							System.out.println("Request Time: " + times);
					
							firstrun = false;
						}
						
						if(!automate)
							System.out.print("Press <enter> to continue");
						reader.readLine();
					}
					
					//we're done
					done = true;
				}
				else
				{
					System.out.println("That is an invalid id. Please try again.");
				}
			}while(!done);
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private void DoReadThreaded(ISimpleService<Person> service, int option, long threadNumber)
	{
		StringBuilder results = new StringBuilder();
		
		try
		{
			results.append("\t\t<thread pid=\"" + threadNumber + "\">\n");
			
			for(int x=0; x<testConfig.getLightLoadSampleSize(); x++)
			{
				long elapsed = 0;
				long ms = System.currentTimeMillis();
				Person person = service.Read(option);
				elapsed = System.currentTimeMillis() - ms;
				
				results.append("\t\t\t<time>" + elapsed + "</time>\n");
				
				if(person == null)
					System.out.println("Person not found.");
				else
					System.out.println("[" + Thread.currentThread().getId() + "] Found: " + person.toString());
			}
			
			results.append("\t\t</thread>\n");
			
			WriteResults(LIGHT_LOAD_FILENAME, results.toString());
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private synchronized static void WriteResults(String testType, String results)
	{
		//output the results to a file
		if(testType == null)
			testType = "testfile.txt";
		
		try
		{
			File file = new File(testType);
			BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
			writer.write(results);
			writer.flush();
			writer.close();
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private static void DoUpdate(ISimpleService<Person> service, BufferedReader reader)
	{
		String option = null;
		boolean done = false;
		
		try
		{
			do
			{
				System.out.print("Enter an id ('q' or 'Q' to quit): ");
				option = reader.readLine();
				
				//validate the input
				if(ValidateInput(option, true))
				{
					if(!option.contentEquals("q") && !option.contentEquals("Q"))
					{
						//attempt to get the record from the service
						Person person = service.Read(Integer.parseInt(option));
						
						if(person == null)
							System.out.println("Person not found.");
						else
							System.out.println("Found: " + person.toString());
						
						//just testing... update the first and last name
						System.out.print("Enter first name (original " + person.getFirstName() + "): ");
						
						//for testing.. just set it
						person.setFirstName(reader.readLine());
						
						System.out.print("Enter last name (original " + person.getLastName() + "): ");
						
						//for testing.. just set it
						person.setLastName(reader.readLine());
						
						//update it
						if(service.Update(person))
							System.out.println("Update successful.");
						else
							System.out.println("Update failed. Check server log for details.");
						
						System.out.print("Press <enter> to continue");
						reader.readLine();
					}
					
					//we're done
					done = true;
				}
				else
				{
					System.out.println("That is an invalid id. Please try again.");
				}
			}while(!done);
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private static void DoList(ISimpleService<Person> service, BufferedReader reader, boolean automate)
	{
		try
		{
			boolean firstrun = true;
			String times = "";
			int i = 0;
			while(automate || firstrun)
			{
				if(automate)
					Thread.sleep(1000);
				//simple, just get the list from the service
				
				long elapsed = 0;
				long ms = System.currentTimeMillis();
				Iterable<Person> personList = service.List();
				elapsed = System.currentTimeMillis() - ms;
				if(!automate)
				{
					System.out.println("Printing db contents:");
					for(Person p : personList)
						System.out.println(p.toString());
					
					System.out.println("");
					System.out.print("Press <enter> to continue");
				}
				times = i + ": " + elapsed + "ms";
				i++;
				System.out.println("Request Time: " + times);
				firstrun = false;
			}
		}
		catch (RemoteException e)
		{
			System.out.println("Error: " + e.toString());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		try 
		{
			reader.readLine();
		} 
		catch (IOException e) 
		{	
			//cry a little inside due to poor exception handling...
			e.printStackTrace();
		}
	}
	
	private void DoListThreaded(ISimpleService<Person> service, long threadNumber)
	{
		StringBuilder results = new StringBuilder();
		
		try
		{
			results.append("\t\t<thread pid=\"" + threadNumber + "\">\n");
			
			for(int x=0; x<testConfig.getHeavyLoadSampleSize(); x++)
			{
				long elapsed = 0;
				long ms = System.currentTimeMillis();
				service.List();
				elapsed = System.currentTimeMillis() - ms;
				
				results.append("\t\t\t<time>" + elapsed + "</time>\n");
			}
			
			results.append("\t\t</thread>\n");
			
			WriteResults(HEAVY_LOAD_FILENAME, results.toString());
		}
		catch(Exception ex)
		{
			//cry a little inside due to poor exception handling...
			ex.printStackTrace();
		}
	}
	
	private static boolean ValidateInput(String input, boolean checkNumeric)
	{
		if(input == null)
			return false;
		
		if(input.length() <= 0)
			return false;
		
		//if it's a quit, then don't check anything else
		if(input.contentEquals("Q") || input.contentEquals("q"))
			return true;
		
		//check numeric
		if(checkNumeric)
		{
			try
			{
				Integer.parseInt(input);
			}
			catch(NumberFormatException ex)
			{
				return false;
			}
		}
		
		return true;
	}
}
